Skip to content

Conversation

blegat
Copy link
Member

@blegat blegat commented Sep 2, 2025

The current approach causes small type instability which amplifies into bigger one down the line in JuliaAlgebra/MultivariateBases.jl#57 just for the sake of having Base.IteratorSize type-stable.
Since Base.IteratorSize isn't so critical, it's probably better to have everything type-stable expect IteratorSize.
This is the approach taken in this PR.

Comment on lines 475 to 480
function Base.IteratorSize(it::ExponentsIterator{M,Nothing}) where {M}
if isempty(it.object)
return Base.HasLength()
end
return Base.IsInfinite()
end
Copy link
Contributor

@nsajko nsajko Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@blegat blegat Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IteratorSize should be define on the type, not the value.

It is but if you look at the implementation of collect(::Any), it is calling IteratorSize on the value which then redirect to calling IteratorSize on the type so it this implementation actually passes all my tests checking consistency with collect etc...
So the only issue will be that users that call IteratorSize on the type in the case they created the exponent iterator with a list of 0 variables will get Base.IsInfinite while in fact the iterator only has one element. The fact that this gives IsInfinite will just prevent some code to work while it could have worked if it was finite but that code is already not working when there is more than 0 variables so I don't see a use case being broken by that. I actually need IsInfinite for the use with. Indeed, SizeUnknown would be a bit safer here but on the other hand, the case with zero variable is kind of a corner case and IsInfinite may also be understood as MaybeIsInfinite, not sure if there is any code that relies on it being actually infinite :/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the good compromise it to just return SizeUnknown when it's called on the type

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah this is exactly what's done for Iterators.Cycle, thanks for the link, this is exactly the same case, interesting

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IteratorSize should be define on the type, not the value.

It is but if you look at the implementation [...]

  • I interpret the iteration interface like so:

    • Callers of IteratorSize(::Any) should be able to assume the call is "free", in the sense it gets constant folded and does not exist at run time when everything is concretely inferred.

    • The intention behind the IteratorSize(x) = IteratorSize(typeof(x)) is as a mere convenience for making it unnecessary to call typeof. So this method is supposed to be the only method of IteratorSize that accepts non-Type.

    • Callers should be able to assume IteratorSize(x) === IteratorSize(typeof(x)), if !isa(x, Type).

    • If one wants a trait like IteratorSize, but defined on instances, they should create a new function instead of adding methods to IteratorSize.

  • Another point possibly worth considering is abstract inference: when the argument types to a call are not concretely known, Julia will possibly consider a set of more than one matching method. Thus adding a methods to a callable can negatively affect the code generation (and resistance to invalidation) for unrelated loaded packages. This is especially bad if the method is of an uncommon type.

Comment on lines +473 to +474
# in an instance or on the type. `Iterators.Cycle` has the same behavior,
# see https://github.com/JuliaLang/julia/pull/54187
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Iterators.Cycle has the same behavior

That's not correct, the PR is not merged.

Copy link
Member Author

@blegat blegat Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I wasn't planning to merge this one either until that one is merged or reaches a consensus.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants